﻿// HSS.Interlink.UploadFileViewModel.cs
// ----------------------------------------------------------------------------
// Licensed under the MIT license (http://www.opensource.org/licenses/mit-license.html)
// ----------------------------------------------------------------------------
// HighSpeed-Solutions, LLC
// Copyright (c) 2001-2010
// ----------------------------------------------------------------------------
// File:       UploadFileViewModel.cs
// Author:     HSS\gbanta
// Created:    07/16/2011
// Modified:   07/22/2011
// ----------------------------------------------------------------------------
namespace HSS.Interlink
{
	#region Using Directives
	using System;
	using System.ComponentModel;
	using System.IO;
	using System.Text;
	using System.Windows.Threading;
	using System.Windows.Input;
	using System.Windows.Controls;
	using System.Collections.ObjectModel;
	using System.Windows;
	#endregion

	#region FileStates
	/// <summary>
	/// Possible States for a File that is going to be uploaded.
	/// </summary>
	public enum FileStates
	{
		/// <summary>
		/// The file is ready to be uploaded.
		/// </summary>
		Ready,
		/// <summary>
		/// The file upload processes has begun and this file is in queue.
		/// </summary>
		Pending,
		/// <summary>
		/// The file is currently being uploaded.
		/// </summary>
		Uploading,
		/// <summary>
		/// The file has completed the uploaded process.
		/// </summary>
		Finished,
		/// <summary>
		/// The file failed the upload process and is in an error state.
		/// </summary>
		Error,
		/// <summary>
		/// The file is queued to be canceled.
		/// </summary>
		CancelPending,
		/// <summary>
		/// The file is currently being canceled.
		/// </summary>
		Canceling,
		/// <summary>
		/// The file has been canceled.
		/// </summary>
		Canceled,
		/// <summary>
		/// The file has been removed from the list of files to be uploaded.
		/// </summary>
		Removed
	}
	#endregion

	#region UploadFileViewModel
	/// <summary>
	/// UploadFileViewModel contains the File to be uploaded from a Users Computer
	/// </summary>
	public sealed class UploadFileViewModel : INotifyPropertyChanged
	{
		#region Events

		#region INotifyPropertyChanged Members
		/// <summary>
		/// Notify listeners of Change
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;
		private void OnPropertyChanged(string propertyName)
		{
			this.ownerViewModel.Invoke(() =>
			{
				var handlerInternal = this.PropertyChanged;
				if (null == handlerInternal)
					return;
				handlerInternal(this, new PropertyChangedEventArgs(propertyName));
			});
		}
		#endregion

		#endregion

		#region Fields
		FileInfo fileInfo;
		ContextMenu contextMenu;
		UploadFileWriter writer;
		MouseClickManager clickManager;
		UploadFileDialogViewModel ownerViewModel;
		internal Func<UploadFileViewModel, string> GetMetadata;
		internal string UploadUri;
		internal string HandlerKey;
		double defaultFileNameWidth = 215;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructor
		/// </summary>
		internal UploadFileViewModel()
		{

		}
		#endregion

		#region Properties

		#region Public

		/// <summary>
		/// Gets the name of the file.
		/// </summary>
		public string FileName
		{
			get { return this.fileInfo.Name; }
		}
		/// <summary>
		/// Gets the size, in bytes, of the current file.
		/// </summary>
		public long FileLength
		{
			get
			{
				if (!_fileSize.HasValue)
					_fileSize = this.fileInfo.Length;
				return _fileSize.Value;
			}
		} long? _fileSize;
		/// <summary>
		/// Gets the last exception message.
		/// </summary>
		public string LastException
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the number of bytes that have been uploaded.
		/// </summary>
		public double BytesUploaded
		{
			get { return _bytesUploaded; }
			internal set
			{
				_bytesUploaded = value;
				this.OnPropertyChanged("BytesUploaded");
			}
		} double _bytesUploaded = 0;
		/// <summary>
		/// Gets the Percentage of bytes that have been uploaded.
		/// </summary>
		public int Percentage
		{
			get { return _percentage; }
			internal set
			{
				if (_percentage != value)
				{
					_percentage = value;
					OnPropertyChanged("Percentage");
				}
			}
		} int _percentage = 0;
		/// <summary>
		/// Gets the current state of the File (Uploading, Canceled etc.)
		/// </summary>
		public FileStates State
		{
			get { return _state; }
			internal set
			{
				if (_state != value)
				{
					_state = value;
					if (null != this.View)
					{
						this.ownerViewModel.Invoke(() =>
						{
							System.Windows.VisualStateManager.GoToState(this.View, this.State.ToString(), true);
						});
					}
					OnPropertyChanged("State");
				}
			}
		} FileStates _state = FileStates.Ready;
		/// <summary>
		/// Gets if the file upload process has completed (If true; resultant State is either FileStates.Error, FileStates.Canceled or FileStates.Finished)
		/// </summary>
		public bool IsCompleted
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets if the File has been closed
		/// </summary>
		public bool IsClosed
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the text to display in the ListItem ToolTip
		/// </summary>
		public string ToolTipText
		{
			get
			{
				string byteString = new ByteConverter().CovertToString(this.FileLength);
				return this.FileName + "  ( " + byteString + " )";
			}
		}
		/// <summary>
		/// Gets the Start Timestamp
		/// </summary>
		public DateTime StartTimestamp
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the End Timestamp
		/// </summary>
		public DateTime EndTimestamp
		{
			get { return _endTimestamp; }
			private set
			{
				_endTimestamp = value;
				this.OnPropertyChanged("ElapsedTime");
				this.OnPropertyChanged("TransferRate");
			}
		} DateTime _endTimestamp;
		/// <summary>
		/// Gets the elapsed time.
		/// </summary>
		public TimeSpan ElapsedTime
		{
			get
			{
				if (EndTimestamp == StartTimestamp || StartTimestamp == DateTime.MinValue)
					return new TimeSpan(0, 0, 0);
				return EndTimestamp.Subtract(StartTimestamp);
			}
		}
		/// <summary>
		/// Gets the elapsed time expressed as total seconds.
		/// </summary>
		public double ElapsedSeconds
		{
			get
			{
				return ElapsedTime.TotalSeconds;
			}
		}
		/// <summary>
		/// Gets the transfer rate.
		/// </summary>
		public double TransferRate
		{
			get
			{
				if (this.BytesUploaded == 0 || this.ElapsedSeconds == 0)
					return 0;
				return this.BytesUploaded / this.ElapsedSeconds;
			}
		}
		/// <summary>
		/// Gets or sets the IsSelected value.
		/// </summary>
		public bool IsSelected
		{
			get { return this._isSelected; }
			set
			{
				this._isSelected = value;
				this.OnPropertyChanged("IsSelected");
				if (value)
					this.ownerViewModel.SelectedFile = this;
			}
		} bool _isSelected;

		#endregion

		#region Internal
		/// <summary>
		/// Gets or sets the current View
		/// </summary>
		internal UploadFileView View
		{
			get;
			set;
		}
		/// <summary>
		/// Gets the Stream for this file
		/// </summary>
		internal Stream FileStream
		{
			get;
			private set;
		}
		/// <summary>
		/// Gets the Internal Debug StringBuilder.
		/// </summary>
		internal StringBuilder DebugString
		{
			get { return _debugString; }
		} StringBuilder _debugString = new StringBuilder();
		/// <summary>
		/// Gets the contents of the Debug String.
		/// </summary>
		internal string DebugMessage
		{
			get
			{
				return DebugString.ToString();
			}
		}
		/// <summary>
		/// Indicates if we're debug writing.
		/// </summary>
		internal bool IsDebugWriting
		{
			get;
			set;
		}

		#endregion

		#region Commands
		/// <summary>
		/// Gets the Cancel Command
		/// </summary>
		public ICommand CancelCommand
		{
			get
			{
				if (null == _cancelCommand)
					_cancelCommand = new CancelCommand(this);
				return _cancelCommand;
			}
		} ICommand _cancelCommand;
		/// <summary>
		/// Gets the Remove Command
		/// </summary>
		public ICommand RemoveCommand
		{
			get
			{
				if (null == _removeCommand)
					_removeCommand = new RemoveCommand(this);
				return _removeCommand;
			}
		} ICommand _removeCommand;
		/// <summary>
		/// Gets the ShowDetail Command
		/// </summary>
		public ICommand ShowDetailCommand
		{
			get
			{
				if (null == _showDetailCommand)
					_showDetailCommand = new ShowDetailCommand(this);
				return _showDetailCommand;
			}
		} ICommand _showDetailCommand;
		#endregion

		#endregion

		#region Methods
		/// <summary>
		/// Creates an instance in a failed state. This allows it show in the list but not be processed.
		/// </summary>
		/// <param name="ownerControlViewModel">The owning UploadFileDialog.</param>
		/// <param name="fileInfo">The File being uploaded.</param>
		/// <param name="errorMessage">The reason for the error.</param>
		internal static void CreateAsFailed(UploadFileDialogViewModel ownerControlViewModel, FileInfo fileInfo, string errorMessage)
		{
			UploadFileViewModel view = new UploadFileViewModel();
			view.fileInfo = fileInfo;
			view.ownerViewModel = ownerControlViewModel;
			view.LastException = errorMessage;
			view.State = FileStates.Error;
			view.UploadCompleted(0);
			ownerControlViewModel.Files.Add(view);
		}

		internal void Initialize(UploadFileDialogViewModel ownerControlViewModel, FileInfo fileInfo)
		{
			this.fileInfo = fileInfo;
			this.ownerViewModel = ownerControlViewModel;
			this.IsDebugWriting = ownerControlViewModel.IsDebugWriting;
			this.HandlerKey = ownerControlViewModel.HandlerKey;
			this.GetMetadata = ownerControlViewModel.GetMetadata;
			if (fileInfo.Length == 0)
			{
				this.LastException = "Zero length file, nothing to upload.";
				this.State = FileStates.Error;
				this.UploadCompleted(0);
			}
			else
				this.LastException = string.Empty;
		}
		internal void InitializeView(UploadFileView view)
		{
			this.View = view;
			this.clickManager = new MouseClickManager(this.View);
			this.ownerViewModel.Invoke(() =>
				{
					this.SubscribeToEvents();
				});
			System.Windows.VisualStateManager.GoToState(this.View, this.State.ToString(), false);
			this.View.txtFileName.Width = defaultFileNameWidth;
		}
		internal void UnintializeView()
		{
			this.ownerViewModel.Invoke(() =>
				{
					this.UnsubscribeToEvents();
				});
		}
		internal void InitializeFileNameWidth(double width)
		{
			this.defaultFileNameWidth = width;
		}
		internal void UpdateFileNameWidth(double newWidth)
		{
			if (null == this.View)
				return;
			this.View.txtFileName.Width = newWidth;
		}

		internal void SetPending(Uri uri)
		{
			if (this.IsCompleted || this.IsClosed)
				return;
			this.UploadUri = uri.ToString();
			this.State = FileStates.Pending;
		}
		internal bool BeginUpload()
		{
			// No re-entrance
			if (this.IsCompleted || this.IsClosed)
				return false;

			if (this.State != FileStates.Pending)
				return false;

			try
			{
				this.State = FileStates.Uploading;
				this.StartTimestamp = DateTime.Now;
				this.EndTimestamp = this.StartTimestamp;
				this.FileStream = this.fileInfo.OpenRead();
				this.IsSelected = true;
				string metadata = this.GetMetadata(this);
				this.writer = new UploadFileWriter();
				this.writer.BeginWrite(this, metadata, this.HandleFileAlreadyExists);
				return true;
			}
			catch (Exception ex)
			{
				this.UploadError(ex.Message);
				this.UploadCompleted(0);
			}
			return false;
		}
		internal void HandleFileAlreadyExists(string fileName, Action<bool> callback)
		{
			this.ownerViewModel.Invoke(() =>
				{
					// Custom handler
					if (null != this.ownerViewModel.view.FileAlreadyExistsHandler)
					{
						callback(this.ownerViewModel.view.FileAlreadyExistsHandler(fileName));
						return;
					}

					// Fallback handler
					if (this.ownerViewModel.view.AllowFileOverwrite && MessageBox.Show("File already exists; overwrite?", "File Exists", MessageBoxButton.OKCancel) == MessageBoxResult.OK)
						callback(true);
					else
						callback(false);
				});
		}
		internal bool CanCancelUpload()
		{
			return this.State == FileStates.Uploading;
		}
		internal void CancelUpload()
		{
			// If uploading has begun, inform
			// uploader to send a cancel request
			if (null != writer)
			{
				writer.RequestCancel();
			}
			else
			{
				// If this File is waiting in line to 
				// be processed (pending), go ahead
				// and set it's state to canceled
				if (this.State == FileStates.Pending)
				{
					this.UploadCompleted(this.BytesUploaded);
					this.State = FileStates.Canceled;
				}
			}
		}
		internal bool CanRemove()
		{
			return this.State == FileStates.Ready;
		}
		internal void Remove()
		{
			this.Close();
			this.ownerViewModel.RemoveFile(this);
		}
		internal bool CanShowDetail()
		{
			return this.State == FileStates.Ready ||
				this.State == FileStates.Finished ||
				this.State == FileStates.Canceled ||
				this.State == FileStates.Error;
		}
		internal void ShowDetail()
		{
			this.ownerViewModel.ShowDetail(this);
		}
		internal void Close()
		{
			if (this.IsClosed)
				return;
			this.IsClosed = true;
			this.writer = null;
			if (null != this.FileStream)
			{
				this.FileStream.Close();
				this.FileStream.Dispose();
			}
			this.FileStream = null;
		}

		internal void UploadProgress(long totalBytesSent, long bytesSent)
		{
			this.Percentage = (int)((totalBytesSent * 100) / this.FileLength);
			this.ownerViewModel.FileProgress(bytesSent);
		}
		internal void UploadError(string exceptionMessage)
		{
			this.LastException = exceptionMessage;
			this.State = FileStates.Error;
		}
		internal void UploadCanceling()
		{
			this.State = FileStates.Canceling;
		}
		internal void UploadCanceled()
		{
			this.State = FileStates.Canceled;
			if (!string.IsNullOrEmpty(this.LastException))
				this.State = FileStates.Error;
		}
		internal void UploadFinished()
		{
			this.State = FileStates.Finished;
			this.ownerViewModel.FileFinished();
		}
		internal void UploadCompleted(double totalBytesSent, string result = "")
		{
			this.EndTimestamp = DateTime.Now;
			this.Close();
			this.BytesUploaded = totalBytesSent;
			this.IsCompleted = true;
			this.ownerViewModel.FileCompleted(this.FileName, result);
		}


		void SubscribeToEvents()
		{
			if (null != this.clickManager)
				this.clickManager.DoubleClick += new MouseButtonEventHandler(UploadFileView_DoubleClick);

			if (null != this.View)
			{
				this.View.MouseLeftButtonUp += new MouseButtonEventHandler(UploadFileView_MouseLeftButtonUp);
				this.View.MouseRightButtonDown += new MouseButtonEventHandler(UploadFileView_MouseRightButtonDown);
				this.View.MouseRightButtonUp += new MouseButtonEventHandler(UploadFileView_MouseRightButtonUp);
			}
		}
		void UnsubscribeToEvents()
		{
			if (null != this.clickManager)
				this.clickManager.DoubleClick -= new MouseButtonEventHandler(UploadFileView_DoubleClick);

			if (null != this.View)
			{
				this.View.MouseLeftButtonUp -= new MouseButtonEventHandler(UploadFileView_MouseLeftButtonUp);
				this.View.MouseRightButtonDown -= new MouseButtonEventHandler(UploadFileView_MouseRightButtonDown);
				this.View.MouseRightButtonUp -= new MouseButtonEventHandler(UploadFileView_MouseRightButtonUp);
			}
		}
		#endregion

		#region Event Handlers
		void UploadFileView_DoubleClick(object sender, MouseButtonEventArgs e)
		{
			if (this.ShowDetailCommand.CanExecute(null))
				this.ShowDetail();
		}
		void UploadFileView_MouseLeftButtonUp(object s, MouseButtonEventArgs e)
		{
			this.clickManager.HandleClick(s, e);
		}
		void UploadFileView_MouseRightButtonDown(object s, MouseButtonEventArgs e)
		{
			e.Handled = true;
		}
		void UploadFileView_MouseRightButtonUp(object s, MouseButtonEventArgs e)
		{
			e.Handled = true;
			this.IsSelected = true;
			ObservableCollection<ContextMenuItem> menuItems = new ObservableCollection<ContextMenuItem>();

			menuItems.Add(new TextContextMenuItem() { Text = "Cancel", Execute = this.CancelUpload, CanExecute = this.CanCancelUpload, Image = ResourceMgr.CancelImage });
			menuItems.Add(new TextContextMenuItem() { Text = "Remove", Execute = this.Remove, CanExecute = this.CanRemove, Image = ResourceMgr.RemoveImage });
			menuItems.Add(new TextContextMenuItem() { Text = "View Info", Execute = this.ShowDetail, CanExecute = this.CanShowDetail, Image = ResourceMgr.InformationImage });

			if (menuItems.Count > 0)
			{
				contextMenu = null;
				contextMenu = new ContextMenu(this.ownerViewModel.view);
				foreach (var item in menuItems)
					contextMenu.MenuItems.Add(item);
				contextMenu.Show(e.GetPosition(null));
			}
		}
		#endregion
	}
	#endregion
}